package com.tarena.csmall.front.web.repository;

import cn.tedu.mall.pojo.product.vo.SpuListItemVO;
import cn.tedu.mall.pojo.product.vo.SpuStandardVO;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import com.tarena.csmall.front.web.mapper.SpuMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

@Repository
@Slf4j
public class SpuRepositoryImpl implements SpuRepository{
    @Autowired
    private SpuMapper spuMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    //业务参数 categoryId
    @Override public List<SpuListItemVO> getSpus(Long categoryId) {
        //组织读取缓存数据
        String spusKey="spus:cat:"+categoryId;
        //判断缓存是否命中
        Boolean hasKey = redisTemplate.hasKey(spusKey);
        //list对象操作
        ListOperations<String, String> opsForList = redisTemplate.opsForList();
        if (hasKey){
            //从缓存拿回数据
            List<String> cacheSpus = opsForList.range(spusKey, 0, -1);
            //反序列化成对象
            List<SpuListItemVO> spus=new ArrayList<>();
            for (String spuJson : cacheSpus) {
                SpuListItemVO spu=JSON.toJavaObject(JSON.parseObject(spuJson),SpuListItemVO.class);
                spus.add(spu);
            }
            return spus;
        }else{
            //读数据库
            List<SpuListItemVO> spus = spuMapper.seletcSpusByCategoryId(categoryId);
            //是否读到空
            if (spus==null||spus.size()==0){
                return null;
            }
            List<String> cacheSpus=new ArrayList<>();
            for (SpuListItemVO spu : spus) {
                //序列化成json
                String spuJson = JSON.toJSONString(spu);
                cacheSpus.add(spuJson);
            }
            //存储到redis
            opsForList.leftPushAll(spusKey,cacheSpus);
            return spus;
        }
    }

    /**
     * 读取spu数据
     * 满足缓存流程
     * @param id
     * @return
     */
    @Override public SpuStandardVO getSpu(Long id) {
        // 1 判断缓存存在
        String cacheKey="spu:"+id;
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (hasKey){
            //使用缓存封装数据
            String spuJson = opsForValue.get(cacheKey);
            if (StringUtils.isEmpty(spuJson)){
                return null;
            }
            return JSON.toJavaObject(JSON.parseObject(spuJson),SpuStandardVO.class);
        }else{
            // 抢锁 因为缓存未命中,避免大量请求读数据库造成击穿
            String lockKey="spu:lock:"+id;
            // randCode 4位随机数
            String randCode=new Random().nextInt(9000)+1000+"";
            try{
                //tryLock 抢锁
                Boolean tryLock = opsForValue.setIfAbsent(lockKey, randCode, 10, TimeUnit.SECONDS);
                if(!tryLock){
                    //抢锁失败,等待5秒钟,重新读取缓存
                    Thread.sleep(5000);
                    String spuJson=opsForValue.get(cacheKey);
                    if (StringUtils.isEmpty(spuJson)){
                        return null;
                    }
                    //没读到null说明等待5秒钟时间之内,别的线程已经把缓存更新了
                    return JSON.toJavaObject(JSON.parseObject(spuJson),SpuStandardVO.class);
                }
                // 2 读取数据库更新缓存
                SpuStandardVO spu=spuMapper.selectSpuByPrimaryKey(id);
                // 3 判断数据库是否存在
                if (spu==null){
                    //本次请求是一次穿透,存储短期的null
                    opsForValue.set(cacheKey,null,10,TimeUnit.SECONDS);
                    return null;
                }
                //序列化 存入redis
                String spuJson=JSON.toJSONString(spu);
                //单个商品数据的缓存,应该设置一下超时时间
                //超时时间是随机的,防止雪崩
                opsForValue.set(cacheKey,spuJson,60*60*24*2+new Random().nextInt(60*60*24), TimeUnit.SECONDS);
                return spu;
            }catch (Exception e){
                log.error("当前抢锁读数据异常",e);
                return null;
            }finally {
                String cacheRand = opsForValue.get(lockKey);
                if(cacheRand!=null && cacheRand.equals(randCode)){
                    //只有条件满足,才释放,其他都不执行del操作
                    redisTemplate.delete(lockKey);
                }
            }
        }
    }
}
